package incrpatch

import (
	"reflect"
	"strconv"
	"strings"

	"gitee.com/terender/sfw/rlog"
)

// ReflectTable 数据实体的固定字段全反射表
// 用于在使用反射方法通过字段名 读取|写入 字段数据时，可以快速定位到字段变量的反射值，不需要再重新反射查找
type ReflectTable struct {
	root reflect.Value
	keys []string
	m    map[string]reflect.Value
}

// NewReflectTable 构造一个数据实体的固定字段全反射表
func NewReflectTable(root reflect.Value) *ReflectTable {
	rt := &ReflectTable{
		root: root,
		m:    make(map[string]reflect.Value),
	}
	rt.extract(``, rt.root)
	return rt
}

// GetValue 获取固定字段全反射表中保存的指定字段的反射值
func (rt *ReflectTable) GetValue(key string) (reflect.Value, bool) {
	value, ok := rt.m[key]
	return value, ok
}

// FindParentOf 在固定字段全反射表中找到 key 对应字段所属的父节点的反射值, 及其在父节点下的 subkey
func (rt *ReflectTable) FindParentOf(key string) (reflect.Value, []string) {
	segments := strings.Split(key, ".")
	for i := len(segments) - 1; i > 0; i-- {
		parentKey := strings.Join(segments[0:i], ".")
		v, ok := rt.m[parentKey]
		if ok {
			return v, segments[i:]
		}
	}
	return rt.root, segments
}

// extract 递归提取一个数据实体的所有成员字段的全路径 key 及其反射值
// 全路径 key 以 `.` 连接，所有字段名全小写
// 对于 struct 值类型成员字段，继续递归其子结构
// 对于 map、数组、slice 类型成员字段，不再获取其成员的子结构
func (rt *ReflectTable) extract(path string, value reflect.Value) {
	// 如果是指针，则必须是结构体指针
	if value.Kind() == reflect.Ptr && value.Type().Elem().Kind() != reflect.Struct {
		rlog.Warnf(`数据实体中不能包含非 *struct 的指针变量`)
		return
	}

	// 如果是 map，则其 map-key 必须是 string
	if value.Kind() == reflect.Map && value.Type().Key().Kind() != reflect.String {
		rlog.Warnf(`数据实体中的 map 类型字段[%v]的 key 不是字符串`, value.Type())
		return
	}

	// 当前反射数据保存至反射表
	if path != `` {
		rt.keys = append(rt.keys, path)
		rt.m[path] = value
	}

	// 如果当前字段的原始类型是结构体，则遍历结构体的全部成员字段
	if value.Kind() == reflect.Struct {
		for j := 0; j < value.NumField(); j++ {
			// The Type's StructField for a given field is checked to see if StructField.PkgPath
			// is set to determine if the field is exported or not because CanSet() returns false
			// for settable fields.  I'm not sure why.  -mohae
			if value.Type().Field(j).PkgPath != "" {
				continue
			}
			if !value.IsValid() || !value.CanSet() {
				continue
			}
			subpath := path
			if subpath != `` {
				subpath += `.`
			}
			subpath += strings.ToLower(value.Type().Field(j).Name)
			rt.extract(subpath, value.Field(j))
		}
		return
	}

	// 如果当前字段是数组(注意不包括切片)，并且数组的成员类型是基本类型或者结构体
	if value.Kind() == reflect.Array {
		// if !isBaseType(value.Type().Elem()) &&
		// 	value.Type().Elem().Kind() != reflect.Struct {
		// 	return
		// }
		for i := 0; i < value.Len(); i++ {
			subpath := path
			if subpath != `` {
				subpath += `.`
			}
			subpath += strconv.Itoa(i)
			rt.extract(subpath, value.Index(i))
		}
	}
}
